package cn.iocoder.yudao.module.product.service.sku;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;
import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
import cn.iocoder.yudao.module.product.constant.ProductConstant;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSkuSaveReqVO;
import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSkuUpdateStatusReqVO;
import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDenominationDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuWithNullDO;
import cn.iocoder.yudao.module.product.dal.dataobject.sku.SkuDiscountDTO;
import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuMapper;
import cn.iocoder.yudao.module.product.dal.mysql.sku.ProductSkuWithNullMapper;
import cn.iocoder.yudao.module.product.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.product.enums.spu.ProductSkuStatusEnum;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyService;
import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;
import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.*;

/**
 * 商品 SKU Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
@Slf4j
public class ProductSkuServiceImpl implements ProductSkuService {

    @Resource
    private ProductSkuMapper productSkuMapper;

    @Resource
    @Lazy // 循环依赖，避免报错
    private ProductSpuService productSpuService;
    @Resource
    @Lazy // 循环依赖，避免报错
    private ProductPropertyService productPropertyService;
    @Resource
    @Lazy // 循环依赖，避免报错
    private ProductPropertyValueService productPropertyValueService;

    @Resource
    private ProductSkuWithNullMapper productSkuWithNullMapper;

    @Override
    public void deleteSku(Long id) {
        // 校验存在
        validateSkuExists(id);
        // 删除
        productSkuMapper.deleteById(id);
    }

    private ProductSkuDO validateSkuExists(Long id) {
        ProductSkuDO skuDO = productSkuMapper.selectById(id);
        if (skuDO == null) {
            log.warn("sku不存在, skuId = {}", id);
            throw exception(SKU_NOT_EXISTS);
        }
        return skuDO;
    }

    @Override
    public ProductSkuDO getSku(Long id) {
        ProductSkuDO productSkuDO = productSkuMapper.selectById(id);
        if (productSkuDO != null){
            attachPropertyValueListBySkuId(Arrays.asList(productSkuDO));
        }
        return productSkuDO;
    }

    @Override
    public List<ProductSkuDO> getSkuList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return ListUtil.empty();
        }
        List<ProductSkuDO> productSkuDOS = productSkuMapper.selectBatchIds(ids);
        attachPropertyValueListBySkuId(productSkuDOS);
        return productSkuDOS;
    }

    @Override
    public void validateSkuList(List<ProductSkuSaveReqVO> skus, Boolean specType) {
        // 0、校验skus是否为空
        if (CollUtil.isEmpty(skus)) {
            //throw exception(SKU_NOT_EXISTS);
            return;
        }

        // 单规格，赋予单规格默认属性
//        if (ObjectUtil.equal(specType, false)) {
//            ProductSkuSaveReqVO skuVO = skus.get(0);
//            List<ProductSkuSaveReqVO.Property> properties = new ArrayList<>();
//            ProductSkuSaveReqVO.Property property = new ProductSkuSaveReqVO.Property()
//                    .setPropertyId(ProductPropertyDO.ID_DEFAULT).setPropertyName(ProductPropertyDO.NAME_DEFAULT)
//                    .setValueId(ProductPropertyValueDO.ID_DEFAULT).setValueName(ProductPropertyValueDO.NAME_DEFAULT);
//            properties.add(property);
//            skuVO.setProperties(properties);
//            return; // 单规格不需要后续的校验
//        }

        // 1、校验属性项存在
        Set<Long> propertyIds = skus.stream().filter(p -> p.getProperties() != null)
                // 遍历多个 Property 属性
                .flatMap(p -> p.getProperties().stream())
                // 将每个 Property 转换成对应的 propertyId，最后形成集合
                .map(ProductSkuSaveReqVO.Property::getPropertyId)
                .collect(Collectors.toSet());
        List<ProductPropertyDO> propertyList = productPropertyService.getPropertyList(propertyIds);
        if (propertyList.size() != propertyIds.size()) {
            throw exception(PROPERTY_NOT_EXISTS);
        }

        // 2. 校验，一个 SKU 下，没有重复的属性。校验方式是，遍历每个 SKU ，看看是否有重复的属性 propertyId
        //Map<Long, ProductPropertyValueDO> propertyValueMap = convertMap(productPropertyValueService.getPropertyValueListByPropertyId(propertyIds), ProductPropertyValueDO::getId);
        skus.forEach(sku -> {
            Set<Long> skuPropertyIds = convertSet(sku.getProperties(), propertyItem -> propertyItem.getPropertyId());
            if (skuPropertyIds.size() != sku.getProperties().size()) {
                throw exception(SKU_PROPERTIES_DUPLICATED);
            }
        });

        // 3. 再校验，每个 Sku 的属性值的数量，是一致的。
        int attrValueIdsSize = skus.get(0).getProperties().size();
        for (int i = 1; i < skus.size(); i++) {
            if (attrValueIdsSize != skus.get(i).getProperties().size()) {
                throw exception(SPU_ATTR_NUMBERS_MUST_BE_EQUALS);
            }
        }

        // 4. 最后校验，每个 Sku 之间不是重复的
        // 每个元素，都是一个 Sku 的 attrValueId 集合。这样，通过最外层的 Set ，判断是否有重复的.
        Set<Set<String>> skuAttrValues = new HashSet<>();
        for (ProductSkuSaveReqVO sku : skus) {
            // 添加失败，说明重复
            if (!skuAttrValues.add(convertSet(sku.getProperties(), ProductSkuSaveReqVO.Property::getValueName))) {
                throw exception(SPU_SKU_NOT_DUPLICATE);
            }
        }
    }

    @Override
    public void createSkuList(Long spuId, List<ProductSkuSaveReqVO> skuCreateReqList) {
        if (CollUtil.isEmpty(skuCreateReqList)){
            return;
        }
        List<ProductSkuDO> skus = BeanUtils.toBean(skuCreateReqList, ProductSkuDO.class, sku -> sku.setSpuId(spuId));
        productSkuMapper.insertBatch(skus);
    }

    @Override
    public List<ProductSkuDO> getSkuListBySpuId(Long spuId, Set<Long> skuIds) {
        List<ProductSkuDO> productSkuDOS = productSkuMapper.selectListBySpuIdAndSkuIds(spuId, skuIds);
        attachPropertyValueListBySkuId(productSkuDOS);
        return productSkuDOS;
    }

    @Override
    public List<ProductSkuDO> getSkuListBySpuId(Collection<Long> spuIds) {
        if (CollUtil.isEmpty(spuIds)) {
            return Collections.emptyList();
        }
        List<ProductSkuDO> productSkuDOS = productSkuMapper.selectListBySpuId(spuIds);
        attachPropertyValueListBySkuId(productSkuDOS);
        return productSkuDOS;
    }

    @Override
    public void deleteSkuBySpuId(Long spuId) {
        productSkuMapper.deleteBySpuId(spuId);
    }

    //@Override
    private int updateSkuProperty(Long propertyId, String propertyName) {
        // 获取所有的 sku
        List<ProductSkuDO> skuDOList = productSkuMapper.selectList();
        // 处理后需要更新的 sku
        List<ProductSkuDO> updateSkus = new ArrayList<>();
        if (CollUtil.isEmpty(skuDOList)) {
            return 0;
        }
        skuDOList.stream().filter(sku -> sku.getProperties() != null)
                .forEach(sku -> sku.getProperties().forEach(property -> {
                    if (property.getPropertyId().equals(propertyId)) {
                        property.setPropertyName(propertyName);
                        updateSkus.add(sku);
                    }
                }));
        if (CollUtil.isEmpty(updateSkus)) {
            return 0;
        }

        productSkuMapper.updateBatch(updateSkus);
        return updateSkus.size();
    }

    //@Override
    private int updateSkuPropertyValue(Long propertyValueId, String propertyValueName) {
        // 获取所有的 sku
        List<ProductSkuDO> skuDOList = productSkuMapper.selectList();
        // 处理后需要更新的 sku
        List<ProductSkuDO> updateSkus = new ArrayList<>();
        if (CollUtil.isEmpty(skuDOList)) {
            return 0;
        }
        skuDOList.stream()
                .filter(sku -> sku.getProperties() != null)
                .forEach(sku -> sku.getProperties().forEach(property -> {
                    if (property.getValueId().equals(propertyValueId)) {
                        property.setValueName(propertyValueName);
                        updateSkus.add(sku);
                    }
                }));
        if (CollUtil.isEmpty(updateSkus)) {
            return 0;
        }

        productSkuMapper.updateBatch(updateSkus);
        return updateSkus.size();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSkuList(Long spuId, List<ProductSkuSaveReqVO> skus) {
        if (CollUtil.isEmpty(skus)){
            return;
        }

        // 构建属性与 SKU 的映射关系;
        Map<String, Long> existsSkuMap = convertMap(productSkuMapper.selectListBySpuId(spuId),
                ProductSkuConvert.INSTANCE::buildPropertyKey, ProductSkuDO::getId);

        // 拆分三个集合，新插入的、需要更新的、需要删除的
        List<ProductSkuDO> insertSkus = new ArrayList<>();
        List<ProductSkuDO> updateSkus = new ArrayList<>();
        List<ProductSkuDO> allUpdateSkus = BeanUtils.toBean(skus, ProductSkuDO.class, sku -> sku.setSpuId(spuId));
        allUpdateSkus.forEach(sku -> {
            String propertiesKey = ProductSkuConvert.INSTANCE.buildPropertyKey(sku);
            // 1、找得到的，进行更新
            Long existsSkuId = existsSkuMap.remove(propertiesKey);
            if (existsSkuId != null) {
                sku.setId(existsSkuId);
                updateSkus.add(sku);
                return;
            }
            // 2、找不到，进行插入
            sku.setSpuId(spuId);
            insertSkus.add(sku);
        });

        // 执行最终的批量操作
        if (CollUtil.isNotEmpty(insertSkus)) {
            final List<String> properties = insertSkus.stream().map(v -> v.getProperties().stream().map(x -> x.getPropertyName() + ":" + x.getValueName()).collect(Collectors.joining())).collect(Collectors.toList());
            log.info("插入sku, spuId = {}, properties = {}", spuId, properties);
            productSkuMapper.insertBatch(insertSkus);
        }
        if (CollUtil.isNotEmpty(updateSkus)) {
            final List<String> properties = updateSkus.stream().map(v -> v.getProperties().stream().map(x -> x.getPropertyName() + ":" + x.getValueName()).collect(Collectors.joining())).collect(Collectors.toList());
            final List<Long> skuIds = updateSkus.stream().map(v -> v.getId()).collect(Collectors.toList());
            log.info("更新sku, spuId = {}, skuIds = {}, properties = {}", spuId, skuIds, properties);
            updateSkus.forEach(sku -> productSkuMapper.updateById(sku));
        }
        if (CollUtil.isNotEmpty(existsSkuMap)) {
            log.info("删除sku, spuId = {}, skuIds = {}", spuId, existsSkuMap.values());
            productSkuMapper.deleteBatchIds(existsSkuMap.values());
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSkuStock(ProductSkuUpdateStockReqDTO updateStockReqDTO) {
        // 更新 SKU 库存
        updateStockReqDTO.getItems().forEach(item -> {
            if (item.getIncrCount() > 0) {
                productSkuMapper.updateStockIncr(item.getId(), item.getIncrCount());
            } else if (item.getIncrCount() < 0) {
                int updateStockIncr = productSkuMapper.updateStockDecr(item.getId(), item.getIncrCount());
                if (updateStockIncr == 0) {
                    throw exception(SKU_STOCK_NOT_ENOUGH);
                }
            }
        });

        // 更新 SPU 库存
        List<ProductSkuDO> skus = productSkuMapper.selectBatchIds(
                convertSet(updateStockReqDTO.getItems(), ProductSkuUpdateStockReqDTO.Item::getId));
        Map<Long, Integer> spuStockIncrCounts = ProductSkuConvert.INSTANCE.convertSpuStockMap(
                updateStockReqDTO.getItems(), skus);
        productSpuService.updateSpuStock(spuStockIncrCounts);
    }

    // ========== 新增 =========

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSkuStockByCardKeyProductNo(String productNo, Integer incrCount) {
        final List<ProductSkuDO> skuDOList = productSkuMapper.getSkuByCardKeyProductNo(productNo);
        if (CollUtil.isEmpty(skuDOList)){
            return;
        }

        final Map<Long, Integer> spuStockIncrCounts = new HashMap<>();
        for (ProductSkuDO skuDO : skuDOList) {
            productSkuMapper.updateStockIncr(skuDO.getId(), incrCount);
            log.info("增加sku库存, productNo = {}, skuId = {}, incrCount = {}", productNo, skuDO.getId(), incrCount);

            Integer spuStock = spuStockIncrCounts.get(skuDO.getSpuId());
            if (spuStock == null){
                spuStock = 0;
            }
            spuStock += incrCount;
            if (spuStock > ProductConstant.STOCK_MAX){
                spuStock = ProductConstant.STOCK_MAX;
            }
            spuStockIncrCounts.put(skuDO.getSpuId(), spuStock);
        }

        productSpuService.updateSpuStock(spuStockIncrCounts);
        log.info("增加spu库存, productNo = {}, spuStockIncrCounts = {}", productNo, spuStockIncrCounts);
    }

    @Override
    public void updateSkuList(List<ProductSkuDO> skus){
        productSkuMapper.updateBatch(skus);
    }

    /**
     * 查询spu，属性值从json字段中获取
     * @param spuId
     * @return
     */
    @Override
    public List<ProductSkuDO> getSkuListBySpuIdWithPropertyJson(Long spuId) {
        List<ProductSkuDO> productSkuDOS = productSkuMapper.selectListBySpuId(spuId);
        return productSkuDOS;
    }

    /**
     * 查询并设置sku上的销售属性
     * @param skuDOList
     */
    private void attachPropertyValueListBySkuId(List<ProductSkuDO> skuDOList){
        if (CollUtil.isEmpty(skuDOList)){
            return;
        }

        for (ProductSkuDO sku : skuDOList) {
            sku.setProperties(null);
        }

        // 查询属性值
        final Set<Long> skuIds = skuDOList.stream().map(v -> v.getId()).collect(Collectors.toSet());
        final List<ProductPropertyValueDetailRespDTO> skuPropertyList = productPropertyValueService.getSkuProperty(skuIds);

        if (CollUtil.isNotEmpty(skuPropertyList)){
            // 将propertyList转为skuId2ValueList
            final Map<Long, List<ProductPropertyValueDetailRespDTO>> skuId2ValueList = skuPropertyList.stream().collect(Collectors.groupingBy(ProductPropertyValueDetailRespDTO::getSkuId));

            // 给sku设置属性值
            for (ProductSkuDO sku : skuDOList) {
                final List<ProductPropertyValueDetailRespDTO> valueList = skuId2ValueList.get(sku.getId());
                if (CollUtil.isNotEmpty(valueList)){
                   final List<ProductSkuDO.Property> properties = valueList.stream().map(v -> BeanUtils.toBean(v, ProductSkuDO.Property.class)).collect(Collectors.toList());
                    sku.setProperties(properties);
                }
            }
        }
    }

    /**
     * 创建 SKU
     *
     * @param sku  SKU 对象集合
     */
    @Override
    public <T extends ProductSkuSaveReqVO> Long createSku(T sku) {
        final ProductSkuDO skuDO = BeanUtils.toBean(sku, ProductSkuDO.class);
        productSkuMapper.insert(skuDO);
        return skuDO.getId();
    }

    @Override
    public Long createSku(ProductSkuDO sku) {
        productSkuMapper.insert(sku);
        return sku.getId();
    }

    @Override
    public int updateSku(ProductSkuDO sku) {
        return productSkuMapper.updateById(sku);
    }

    /**
     * 更新 SKU
     *
     * @param skuId 商品 SKU 编号
     * @param sku  SKU 对象集合
     */
    @Override
    public <T extends ProductSkuSaveReqVO> void updateSku(Long skuId, T sku) {
        validateSkuExists(skuId);

        final ProductSkuDO skuDO = BeanUtils.toBean(sku, ProductSkuDO.class);
        productSkuMapper.updateById(skuDO);
    }

    /**
     * 更新 SKU状态
     * @param updateReqVO
     */
    public ProductSkuDO updateSkuStatus(ProductSkuUpdateStatusReqVO updateReqVO) {
        // 校验存在
        final ProductSkuDO skuDO = validateSkuExists(updateReqVO.getId());

        // 更新状态
        skuDO.setStatus(updateReqVO.getStatus());
        productSkuMapper.updateById(skuDO);

        return skuDO;
    }

    /**
     * 更新sku，null值也会更新
     * @param skuId
     * @param sku
     * @param <T>
     */
    @Override
    public <T extends ProductSkuSaveReqVO> void updateSkuWithNullById(Long skuId, T sku){
        validateSkuExists(skuId);

        final ProductSkuWithNullDO skuDO = BeanUtils.toBean(sku, ProductSkuWithNullDO.class);
        productSkuWithNullMapper.updateById(skuDO);
    }

    @Override
    public List<ProductSkuDO> selectListBySpuIdAndStatus(ProductSkuStatusEnum statusEnum, Collection<Long> spuIds) {
        final List<ProductSkuDO> productSkuDOS = productSkuMapper.selectListBySpuIdAndStatus(statusEnum, spuIds);
        attachPropertyValueListBySkuId(productSkuDOS);
        return productSkuDOS;
    }

    @Override
    public List<ProductSkuDenominationDO> selectMinDenominationSkuBySpuIds(List<Long> spuIds) {
        return productSkuMapper.selectMinDenominationSkuBySpuIds(spuIds);
    }

    @Override
    public Map<Long, SkuDiscountDTO> getMaxDiscountSkuBySpuIds(List<Long> spuIds) {
        List<SkuDiscountDTO> skuDiscountList = productSkuMapper.findMaxDiscountSkuBySpuIds(spuIds, LocalDateTime.now());

        return skuDiscountList.stream()
                .collect(Collectors.toMap(SkuDiscountDTO::getSpuId, sku -> sku, (oldValue, newValue) -> newValue));
    }


}
